package com.baijiaoxi.utils.redis;


import com.baijiaoxi.common.model.KeyValueManager;
import com.baijiaoxi.common.model.local.ThreadLocalUtil;
import com.baijiaoxi.common.model.request.RequestInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by shixj on 2018/3/13.
 */
@Slf4j
public class RedisUtil implements KeyValueManager {

    private RedisTemplate<String, Object> redisTemplate;
    private String prefix = "";

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix == null ? "" : prefix;
    }

    public RedisUtil() {
    }


    public RedisUtil(RedisTemplate<String, Object> redisTemplate, String prefix) {
        this.redisTemplate = redisTemplate;
        this.prefix = prefix == null ? "" : prefix;
    }

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    //=============================common============================


    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    /**
     * 模糊获取key
     *
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern) {
        log.debug("keys->keys:{}", pattern);
        return redisTemplate.keys(prefix + pattern).stream().map(key -> key.substring(prefix.length())).collect(Collectors.toSet());
    }


    /**
     * 指定缓存失效时间
     *
     * @param key           键
     * @param expireSeconds 时间(秒)
     * @return
     */
    @Override
    public boolean updateExpireTime(String key, Long expireSeconds) {
        try {
            log.debug("expire->key:{} , seconds:{}", key, expireSeconds);
            if (expireSeconds > 0) {
                redisTemplate.expire(prefix + key, expireSeconds, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        log.debug("getExpire->key:{}", key);
        return redisTemplate.getExpire(prefix + key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        log.debug("hasKey->key:{}", key);
        return hasKeySilent(key);
    }

    public boolean hasKeySilent(String key) {
        try {
            return redisTemplate.hasKey(prefix + key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        log.debug("del->key:{}", key);
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(prefix + key[0]);
            } else {
                redisTemplate.delete(List.of(key).stream().map(tempKey -> prefix + tempKey).toList());
            }
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        log.debug("get->key:{}", key);
        return getSilent(key);
    }

    @Override
    public Object getSilent(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(prefix + key);
    }

    public void setRequestInfo(String key) {
        try {
            RequestInfo requestInfo = ThreadLocalUtil.get();
            redisTemplate.opsForValue().set(prefix + key + "-requestInfo", requestInfo);
        } catch (Exception e) {
            log.error("无法获取requestInfo");
        }
    }

    /*    public FullRequestInfo getRequestInfo(String key) {
            try {
                return (FullRequestInfo) get(prefix + key + "-requestInfo");
            } catch (Exception e) {
                return null;
            }
        }*/
    public RequestInfo getRequestInfoOnRedisExpire(String key) {
        try {
            return (RequestInfo) get(key + "-requestInfo");
        } catch (Exception e) {
            return null;
        }
    }

    public void clearRequestInfo(String key) {
        del(prefix + key + "-requestInfo");
    }


    public boolean set(String key, Object value, boolean recordRequestInfo) {
        log.debug("set->key:{} , value:{}", key, value);
        return setSilent(key, value, recordRequestInfo);
    }

    public boolean setSilent(String key, Object value, boolean recordRequestInfo) {
        try {
            redisTemplate.opsForValue().set(prefix + key, value);
            if (recordRequestInfo)
                setRequestInfo(key);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒) seconds要大于0 如果seconds小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long seconds, boolean recordRequestInfo) {
        log.debug("set->key:{} , value:{} , seconds:{}", key, value, seconds);
        return setSilent(key, value, seconds, recordRequestInfo);
    }

    public boolean setSilent(String key, Object value, long seconds, boolean recordRequestInfo) {
        try {
            if (seconds > 0) {
                redisTemplate.opsForValue().set(prefix + key, value, seconds, TimeUnit.SECONDS);
            } else {
                set(key, value, recordRequestInfo);
            }
            if (recordRequestInfo)
                setRequestInfo(key);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        return set(key, value, false);
    }

    @Override
    public boolean setSilent(String key, Object value) {
        return setSilent(key, value, false);
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒) seconds要大于0 如果seconds小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long seconds) {
        return set(key, value, seconds, false);
    }

    @Override
    public boolean setSilent(String key, Object value, long validSeconds) {
        return setSilent(key, value, validSeconds, false);
    }

    /**
     * 获取过期事件的租户id存储的key
     *
     * @param key
     * @return
     */
    public static String getExpireEventTenantIdKey(String key) {
        return key + "-tenantid";
    }

    /**
     * 获取过期事件的租户id
     *
     * @param key
     * @return
     */
    public Long getExpireEventTenantId(String key) {
        String expireEventTenantIdKey = getExpireEventTenantIdKey(key);
        try {
            String tenantIdStr = (String) get(expireEventTenantIdKey);
            return Long.valueOf(tenantIdStr);
        } catch (Exception e) {
            return 0L;
        }
    }

    /**
     * 注册一个redis过期事件
     *
     * @param tenantId
     * @param key
     * @param value
     * @param seconds
     * @return
     */
    public boolean registerExpireEvent(Long tenantId, String key, Object value, long seconds) {
        set(getExpireEventTenantIdKey(key), String.valueOf(tenantId), seconds + 3600);
        return set(key, value, seconds, false);
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        log.debug("incr->key:{} , delta:{}", key, delta);
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(prefix + key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        log.debug("decr->key:{} , delta:{}", key, delta);
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(prefix + key, -delta);
    }

    //================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        log.debug("hget->key:{} , item:{}", key, item);
        return redisTemplate.opsForHash().get(prefix + key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        log.debug("hmget->key:{} ", key);
        return redisTemplate.opsForHash().entries(prefix + key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        log.debug("hmset->key:{} , map:{}", key, map);
        try {
            redisTemplate.opsForHash().putAll(prefix + key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key     键
     * @param map     对应多个键值
     * @param seconds 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long seconds) {
        log.debug("hmset->key:{} , map:{} , seconds:{}", key, map, seconds);
        try {
            redisTemplate.opsForHash().putAll(prefix + key, map);
            if (seconds > 0) {
                updateExpireTime(key, seconds);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        log.debug("hset->key:{} , item:{} , value:{}", key, item, value);
        try {
            redisTemplate.opsForHash().put(prefix + key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key     键
     * @param item    项
     * @param value   值
     * @param seconds 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long seconds) {
        log.debug("hset->key:{} , item:{} , value:{} , seconds", key, item, value, seconds);
        try {
            redisTemplate.opsForHash().put(prefix + key, item, value);
            if (seconds > 0) {
                updateExpireTime(key, seconds);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        log.debug("hdel->key:{} , item:{}", key, item);
        redisTemplate.opsForHash().delete(prefix + key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        log.debug("hHasKey->key:{} , item:{}", key, item);
        return redisTemplate.opsForHash().hasKey(prefix + key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        log.debug("hincr->key:{} , item:{} , by:{}", key, item, by);
        return redisTemplate.opsForHash().increment(prefix + key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        log.debug("hdecr->key:{} , item:{} , by:{}", key, item, by);
        return redisTemplate.opsForHash().increment(prefix + key, item, -by);
    }

    //============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        log.debug("sGet->key:{} ", key);
        try {
            return redisTemplate.opsForSet().members(prefix + key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        log.debug("sHasKey->key:{} , value:{}", key, value);
        try {
            return redisTemplate.opsForSet().isMember(prefix + key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        log.debug("sSet->key:{} , values:{}", key, values);
        try {
            return redisTemplate.opsForSet().add(prefix + key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key     键
     * @param seconds 时间(秒)
     * @param values  值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long seconds, Object... values) {
        log.debug("sSetAndTime->key:{} , seconds:{} , values:{}", key, seconds, values);
        try {
            Long count = redisTemplate.opsForSet().add(prefix + key, values);
            if (seconds > 0) updateExpireTime(key, seconds);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        log.debug("sGetSetSize->key:{}", key);
        try {
            return redisTemplate.opsForSet().size(prefix + key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        log.debug("setRemove->key:{} , values:{}", key, values);
        try {
            Long count = redisTemplate.opsForSet().remove(prefix + key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        log.debug("lGet->key:{} , start:{} , end:{} ", key, start, end);
        try {
            return redisTemplate.opsForList().range(prefix + key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        log.debug("lGetListSize->key:{}", key);
        try {
            return redisTemplate.opsForList().size(prefix + key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引  index>=0时， 0 表头，1 第二个元素，依次类推；index<0时，-1，表尾，-2倒数第二个元素，依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        log.debug("lGetIndex->key:{} , index:{}", key, index);
        try {
            return redisTemplate.opsForList().index(prefix + key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        log.debug("lSet->key:{} , value:{}", key, value);
        try {
            redisTemplate.opsForList().rightPush(prefix + key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long seconds) {
        log.debug("lSet->key:{} , value:{} , seconds:{}", key, value, seconds);
        try {
            redisTemplate.opsForList().rightPush(prefix + key, value);
            if (seconds > 0) updateExpireTime(key, seconds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        log.debug("lSet->key:{} , value:{}", key, value);
        try {
            redisTemplate.opsForList().rightPushAll(prefix + key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long seconds) {
        log.debug("lSet->key:{} , value:{} , seconds:{}", key, value, seconds);
        try {
            redisTemplate.opsForList().rightPushAll(prefix + key, value);
            if (seconds > 0) updateExpireTime(key, seconds);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        log.debug("lUpdateIndex->key:{} , index:{} , value:{}", key, index, value);
        try {
            redisTemplate.opsForList().set(prefix + key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        log.debug("lRemove->key:{} , count:{} , value:{}", key, count, value);
        try {
            Long remove = redisTemplate.opsForList().remove(prefix + key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * list 头部插入
     */
    public Long lPush(String key, Object value) {
        log.debug("lPush->key:{} , value:{}", key, value);
        return redisTemplate.opsForList().leftPush(prefix + key, value);
    }

    /**
     * list 尾部出（阻塞）
     */
    public Object rpop(String key, Long timeout, TimeUnit timeUnit) {
        Object object = redisTemplate.opsForList().rightPop(prefix + key, timeout, timeUnit);
        if (object != null)
            log.debug("rpop->key:{} , timeout:{} , timeUnit:{},return:{}", key, timeout, timeUnit, object);
        return object;
    }

    /**
     * list 尾部出（立即弹出）
     */
    public Object rpop(String key) {
        Object object = redisTemplate.opsForList().rightPop(prefix + key);
        if (object != null)
            log.debug("rpop->key:{},return:{} ", key, object);
        return object;
    }

    public boolean setIfAbsent(String key, String value, long time, TimeUnit timeUnit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
    }

}
